﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Timers;

namespace PIP.Shared
{
    public class DataManager : IDisposable
    {
        public Int64 Count { get; set; }
        public List<DataContainer> SourceData { get; set; }
        public List<DataContainer> ProgressData { get; set; }
        public List<DataContainer> ResultData { get; set; }
        protected Object lockSource = new Object();
        protected Object lockProgress = new Object();
        protected Object lockResult = new Object();
        public Int32 ColumnsCount;
        public Int32 RowsCount;

        public Int32 ItemWidth;
        public Int32 ItemHeight;
        protected Timer CheckInProgressTimer;

        protected Bitmap OriginalImage;

        public DataManager(Bitmap bitmap, Int32 itemWidth, Int32 itemHeight)
        {
            CheckInProgressTimer = new Timer();

            CheckInProgressTimer.Interval = 5000;
            CheckInProgressTimer.Elapsed += (o, e) => { CheckInProgressData(); };
            CheckInProgressTimer.Start();

            SourceData = new List<DataContainer>();
            ProgressData = new List<DataContainer>();
            ResultData = new List<DataContainer>();

            OriginalImage = bitmap;

            // split bitmap to list of data containers;

            ItemWidth = itemWidth;
            ItemHeight = itemHeight;
            Int32 columnsCount = ColumnsCount = (Int32)Math.Ceiling((Double)(1.0 * bitmap.Width / itemWidth));
            Int32 rowsCount = RowsCount = (Int32)Math.Ceiling((Double)(1.0 * bitmap.Height / itemHeight));
            Count = columnsCount * rowsCount;
            var rectangle = new Rectangle(0, 0, itemWidth, itemHeight);
            for (var i = 0; i < rowsCount; i++)
            {
                if (i == rowsCount - 1)
                {
                    rectangle.Height = OriginalImage.Height - rectangle.Y;
                }
                rectangle.X = 0;
                for (var j = 0; j < columnsCount; j++)
                {

                    if (j == columnsCount - 1)
                    {
                        rectangle.Width = OriginalImage.Width - rectangle.X;
                    }
                    else
                    {
                        rectangle.Width = itemWidth;
                    }
                    var itemIndex = j + columnsCount * i;
                    var @new = bitmap.Clone(rectangle, bitmap.PixelFormat);
                    AddSourceData(new DataContainer(itemIndex, @new));
                    rectangle.X += itemWidth;
                }
                rectangle.Y += itemHeight;
            }

        }


        protected void AddSourceData(DataContainer data)
        {
            lock (lockSource)
            {
                SourceData.Add(data);
            }
        }

        protected void MoveToProgressData(Int32 index)
        {
            lock (lockSource)
            {
                lock (lockProgress)
                {
                    var item = SourceData.Single(t => t.Index == index);
                    if (item != null)
                    {
                        item.StartTime = DateTime.Now;
                        SourceData.Remove(item);
                        ProgressData.Add(item);
                    }
                }
            }
        }

        protected void MoveToResultData(DataContainer dataContainer)
        {
            lock (lockProgress)
            {
                lock (lockResult)
                {
                    var containerInProgress = ProgressData.First(t => t.Index == dataContainer.Index);
                    ProgressData.Remove(containerInProgress);
                    ResultData.Add(dataContainer);
                }
            }
        }

        protected void CheckInProgressData()
        {
            var now = DateTime.Now;
            var lost = ProgressData.Where(t => (now - t.StartTime).TotalSeconds > 5).ToList();
            foreach (var dataContainer in lost)
            {
                lock (lockSource)
                {
                    lock (lockProgress)
                    {
                        dataContainer.StartTime = DateTime.MinValue;
                        ProgressData.Remove(dataContainer);
                        SourceData.Add(dataContainer);
                    }
                }
            }
        }

        public Boolean ResultIsDone()
        {
            if (ResultData.Count < Count)
                return false;
            if (ResultData.Count > Count)
                throw new Exception("Anomality exeption!");

            CheckInProgressTimer.Stop();
            return true;
        }

        public Bitmap GetProcessedImage()
        {
            Int32 itemWidth = ItemWidth;
            Int32 itemHeight = ItemHeight;

            Graphics gr = Graphics.FromImage(OriginalImage);
            foreach (var dataContainer in ResultData)
            {
                var yIndex = dataContainer.Index / ColumnsCount;
                var xIndex = dataContainer.Index - yIndex * ColumnsCount;

                gr.DrawImage(dataContainer.Data, xIndex * itemWidth, yIndex * itemHeight);
            }
            return OriginalImage;
        }

        public List<DataContainer> GetWork(Int32 amount)
        {
            var list = SourceData.Take(amount).ToList();
            foreach (var item in list)
            {
                MoveToProgressData(item.Index);
            }
            return list;
        }

        public void PostWork(DataContainer doneWork)
        {
            MoveToResultData(doneWork);
        }

        public void PostWork(List<DataContainer> doneWork)
        {
            foreach (var item in doneWork)
            {
                MoveToResultData(item);
            }
        }

        public void Dispose()
        {
            CheckInProgressTimer.Stop();
        }
    }
}
